// --- Copyright (c) 2010 Steven Hartgers ---
// Copyright (C) 2010 Steven Hartgers All rights are reserved.
// Author contact: fiinix00/at\gmail.com
// ------------------------------------------------------------------------
// This program is free software: you can redistribute it and/or modify 
// it under the terms of the GNU General Public License as published by 
// the Free Software Foundation, either version 3 of the License, or 
// (at your option) any later version. 
//  
// This program is distributed in the hope that it will be useful, 
// but WITHOUT ANY WARRANTY; without even the implied warranty of 
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
// GNU General Public License for more detail
//  
// You should have received a copy of the GNU General Public License 
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
// ------------------------------------------------------------------------
// COPYRIGHT NOTICE: This copyright notice may NOT be removed, obscured or modified 
// without written consent from the author.
// --- End copyright notice --- 




using System;
using System.Collections;
using System.Net.Sockets;
using System.Threading;
using System.Net.NetworkInformation;

namespace Hack.Net.Smtp
{
    public class TcpServer
    {
        /** The port number to listen to */
        public readonly int port;

        /** Size of thread pool */
        public readonly int poolSize;

        /** Maximum number of connections we accept at a
         * specific instant of time */
        public readonly int maxConn;

        /** The handler which is called for every accepted connection */
        protected ConnectionHandler connhandler;



        /** The listener object for incoming TCP connections */
        private TcpListener listener = null;

        /** The worker which accepts all incoming connections */
        private AcceptWorker acceptWorker = null;
        /** The thread which executes acceptWorker */
        private Thread acceptThread = null;





        /** Create a smtp proxy with specific listen port */
        public TcpServer(int port, int poolSize, int maxConn, ConnectionHandler connhandler)
        {
            this.port = port;
            this.poolSize = poolSize;
            this.maxConn = maxConn;
            this.connhandler = connhandler;
        }


        /**
         * Listen on a specific port for incoming TCP connections
         * and process them.
         * A second call will result in an immediate return.
         */
        public void Start()
        {
            //Listen has already been started...
            if (this.listener != null)
                return;

            //check port
            bool isAvailable = true;
            IPGlobalProperties ipGlobalProperties = IPGlobalProperties.GetIPGlobalProperties();
            TcpConnectionInformation[] tcpConnInfoArray = ipGlobalProperties.GetActiveTcpConnections();

            foreach (TcpConnectionInformation tcpi in tcpConnInfoArray)
            {
                if (tcpi.LocalEndPoint.Port == port)
                {
                    isAvailable = false;
                    break;
                }
            }

            //Start listener
            if (!isAvailable)
            {
                AnytimeMessageBox.Say(
                    String.Format("The port \"{0}\" was in use by another application, using backup port \"2525\"",
                        port),
                    String.Format("Port \"{0}\"in use!",
                        port)
                );
            }
            
            this.listener = new TcpListener((isAvailable ? port : 2525));
            this.listener.Start();
            System.Console.WriteLine("Running at port " + (isAvailable ? port : 2525));

            //Start accept thread
            this.acceptWorker = new AcceptWorker(this.listener, poolSize, maxConn, connhandler);
            this.acceptThread = new Thread(new ThreadStart(this.acceptWorker.accept));
            this.acceptThread.Start();

        }


        /** Stop the server immediately  */
        public void Stop()
        {
            //Abort accepting thread and wait for termination
            acceptWorker.Stop();
            this.acceptThread.Abort();
            this.acceptThread.Join();

            //Stop the listener
            this.listener.Stop();

            //Reset them to null
            this.listener = null;
            this.acceptThread = null;
            this.acceptWorker = null;
        }




        /** This delegate is used to handle accepted connections */
        public delegate void ConnectionHandler(TcpClient client);
    }



    /** This thread does nothing but accepting incoming messages */
    internal class AcceptWorker
    {
        /** Size of thread pool */
        public readonly int poolSize;

        /** Maximum number of connections we accept at a
         * specific instant of time */
        public readonly int maxConn;

        /** The listener to accept from */
        readonly TcpListener listener;


        /** The queue from where connections are taken */
        private CPQueue queue = null;

        /** List of worker threads */
        private ArrayList workerThreads = new ArrayList();



        /** Create worker to accept connections with a thread pool
         * of specific size and specific bound of numbers of connections*/
        public AcceptWorker(TcpListener listener, int poolSize, int maxConn, TcpServer.ConnectionHandler connhandler)
        {
            //Set readonly members
            this.listener = listener;
            this.poolSize = poolSize;
            this.maxConn = maxConn;


            if (poolSize < 1)
                throw new Exception("Thread pool must contain at least one thread.");
            if (maxConn < 1)
                throw new Exception("Thread pool must contain at least one thread.");


            //Create a queue for connections
            this.queue = new CPQueue(maxConn);


            //Create the threads in pool
            for (int i = 0; i < poolSize; i++)
            {
                //Create worker
                HandleWorker w = new HandleWorker(queue, connhandler);

                //Create thread objects and start them
                Thread th = new Thread(new ThreadStart(w.postAccept));
                workerThreads.Add(th);
                th.Start();
            }
        }


        /** Stop accept thread */
        public void Stop()
        {
            //Abort all worker threads in pool...
            foreach (Thread th in workerThreads)
                th.Abort();

            //...and wait for their termination
            foreach (Thread th in workerThreads)
                th.Join();
        }

        /** Work, slave! */
        public void accept()
        {
            while (true)
            {
                //Accept new connection
                TcpClient client = listener.AcceptTcpClient();
                //Enqueue the client
                queue.Enqueue(client);
            }
        }
    }



    /** This thread does the real work.*/
    internal class HandleWorker
    {
        /** The handler which is called for every accepted connection */
        private readonly TcpServer.ConnectionHandler connhandler;

        /** This consumer-producer queue helds all connections */
        private CPQueue queue;


        /** Create new worker for smtp connection */
        public HandleWorker(CPQueue queue, TcpServer.ConnectionHandler connhandler)
        {
            this.queue = queue;
            this.connhandler = connhandler;
        }


        /** Work, slave! */
        public void postAccept()
        {
            while (true)
            {
                try
                {
                    //Get new connection, please
                    TcpClient client = (TcpClient)queue.Dequeue();

                    //Handle this request
                    if (connhandler != null)
                        connhandler(client);

                    //Finished
                    client.Close();
                }
                catch (System.IO.IOException)
                {
                    System.Console.WriteLine("IO error");
                }
            }
        }
    }


}